#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/select.h>
#include <poll.h>
#include <sys/epoll.h>
void* client_thread(void* arg){
  int clientfd=*(int*)arg;
  while(1){
    char buffer[1024]={0};
    int count=recv(clientfd,buffer,sizeof(buffer),0);
    if(count==0){
      close(clientfd);
      break;
    }
    printf("recv:%s\n",buffer);

    count=send(clientfd,buffer,count,0);
    printf("send:%d\n",count); 
  }
  return NULL;
}
int main(){
  int sockfd=socket(AF_INET,SOCK_STREAM,0);

  struct sockaddr_in servaddr;
  servaddr.sin_family=AF_INET;
  servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
  servaddr.sin_port=htons(3000);
  if(-1==bind(sockfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr))){
    printf("bind failed:%s\n",strerror(errno));
  }
  
  listen(sockfd,10);
  printf("listen finish!\n");
  printf("listen fd=%d\n",sockfd);


#if 0
  while(1){
    struct sockaddr_in clientaddr;
    socklen_t len=sizeof(clientaddr);
    int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
    char buffer[1024]={0};
    int count=recv(clientfd,buffer,sizeof(buffer),0);
    printf("recv:%s\n",buffer);

    count=send(clientfd,buffer,count,0);
    printf("send:%d\n",count);
  }
#elif 0
  while(1){
    struct sockaddr_in clientaddr;
    socklen_t len=sizeof(clientaddr);
    int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
    printf("accept a fd=%d\n",clientfd);
    pthread_t tid;
    pthread_create(&tid,NULL,&client_thread,&clientfd);
  }

#elif 0
  //select做io多路复用
  fd_set rfds,rset;//1.首先定义一个读文件描述符集合
  FD_ZERO(&rfds);//2.清空读文件描述符集合
  FD_SET(sockfd,&rfds);//3.将监听套接字加入读文件描述符集合

  int maxfd=sockfd;//4.将监听套接字设置为最大文件描述符，最大文件描述符用于对文件描述符集合做遍历

  while(1){
    rset=rfds;
    //5.将文件描述符集合赋值给读文件描述符集合，在while中调用select,给select传入五个参数，最重要的一点是文件描述符集合这几个参数是输入输出型参数，
    //select返回，就绪的文件描述符会存储在集合中。
    //6.第一个参数最大文件描述符加一，为什么是最大文件描述符加一呢，
    //因为文件描述符是从0开始的，那么可能存在maxfd+1个文件描述符，第二参数是读文件描述符集合，第三个参数是写文件描述符集合，
    //第四个参数是错误文件描述符的集合，第五个是超时时间。返回可写文件描述符，可读文件描述符，错误文件描述符的总和。
    int nready=select(maxfd+1,&rset,NULL,NULL,NULL);

    //7.判断监听套接字的是否在可读文件描述符集合中，如果在说明监听套接字的读事件就绪，需要accept接收新的客户端连接
    if(FD_ISSET(sockfd,&rset)){
      //8.将接收的客户端文件加入文件描述符集合中，更新最大文件描述符
      struct sockaddr_in clientaddr;
      socklen_t len=sizeof(clientaddr);
      int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
      printf("accept a fd=%d",clientfd);
      FD_SET(clientfd,&rfds);
      if(clientfd>maxfd)maxfd=clientfd;
    }

    //9.读文件描述集合中除了监听文件描述符，还有客户端文件描述符需要处理,
    //如果是客服端关闭应该将文件描述符从文件描述符集合中去掉
    for(int i=sockfd+1;i<=maxfd;++i){
      if(FD_ISSET(i,&rset)){
        char buffer[1024]={0};
        int count=recv(i,buffer,1024,0);
        if(count==0){
          close(i);
          printf("disconnect fd:%d\n",i);
          FD_CLR(i,&rfds);
          continue;
        }
        printf("recv:%d\n",count);
        count=send(i,buffer,count,0);
        printf("send:%d\n",count);
      }
    }

  }
#elif 0
  struct pollfd fds[1024]={0};//1.先定义一个存储文件描述符的struct epollfd数组
  fds[sockfd].fd=sockfd;//2.将监听套接字加入struct pollfd数组中，将监听套接字的读事件POLLIN加入events中
  fds[sockfd].events=POLLIN;
  int maxfd=sockfd;//3.定义maxfd,使用sockfd给maxfd赋值
  while(1){
    //4.将struct pollfd数组传入poll中，将maxfd+1传入poll中，以及超时时间传入-1表示一直阻塞直到有事件就绪 返回有几个文件描述符就绪
    int nready=poll(fds,maxfd+1,-1);
    printf("sasa\n");
    if(fds[sockfd].revents&POLLIN){//5.如果有客户端新连接到来 将客户端新连接加入struct pollfd数组  记得将maxfd进行更新
      struct sockaddr_in clientaddr;
      socklen_t len=sizeof(struct sockaddr_in);
      int clientfd=accept(fds[sockfd].fd,(struct sockaddr*)&clientaddr,&len);
      printf("clientfd=%d\n",clientfd);
      fds[clientfd].fd=clientfd;
      fds[clientfd].events=POLLIN;
      if(fds[clientfd].fd>maxfd)maxfd=fds[clientfd].fd;
    }
    for(int i=sockfd+1;i<=maxfd;++i){//6.将客户端新连接的就绪事件进行处理
      if(fds[i].revents&POLLIN){
        char buffer[1024]={0};
        int count=recv(i,buffer,1024,0);
        if(count==0){
          close(fds[i].fd);
          fds[i].events=0;
          fds[i].fd=-1;
          printf("disconnect:%d\n",i);
          continue;
        }
        printf("recv:%d\n",count);
        count=send(i,buffer,count,0);
        printf("send:%d\n",count);
      }

    }
  }
#elif 1
//1.调用epoll_create获取epfd
  int epfd=epoll_create(1);
  //2.将监听套接字的读事件加入epoll_event中
  struct epoll_event ev;
  ev.events=EPOLLIN;
  ev.data.fd=sockfd;
  //3.将epoll_event通过epoll_ctl加入epfd中
  epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);

  while(1){
    //4.定义epoll_event数组接收就绪的文件描述符
    struct epoll_event events[1024];
    int nready=epoll_wait(epfd,events,1024,-1);
    for(int i=0;i<nready;++i){
      int connfd=events[i].data.fd;
      if(connfd==sockfd){//如果就绪文件符和监听套接字是一样的，将就绪文件描述符，获取客户端新连接，通过epoll_ctl加入epoll中
        struct sockaddr_in clientaddr;
        socklen_t len=sizeof(clientaddr);
        int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
        printf("accept a fd=%d\n",clientfd);
        ev.events=EPOLLIN;
        ev.data.fd=clientfd;
        epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
      }
      else if(events[i].events&EPOLLIN){//如果客户端连接的读时间就绪，读取数据，返回回去，
      // 如果客户端关闭，epoll_ctl将客户端文件描述符从epoll中去掉
        char buffer[1024]={0};
        int count=recv(connfd,buffer,1024,0);
        if(count==0){
          close(connfd);
          epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,&ev);
          printf("disconnect:%d\n",connfd);
          continue;
        }
        printf("recv:%d\n",count);
        count=send(connfd,buffer,count,0);
        printf("send:%d\n",count);
      }
    }
  }
#endif

  getchar();
  return 0;
  
}